Exception Handling (Except)
This section describes the OpenDoc exception-handling utility, which resides in the files Except.h and Except.cpp. You can use the exception-handling utility to generate and handle your own exceptions as well as respond to any exceptions generated as a result of calls to OpenDoc. The exception-handling utility implements a simple throw-and-catch exception-handling scheme, similar to those found in some application frameworks and development environments.Using the Exception-Handling Utility
The use of the exception-handling utility is optional. However, if you don't use it you must check the environment variable (ev
) after every SOM call you make and handle it appropriately. The exception-handling utility facilitates this requirement, as described in "Handling SOM Exceptions".To use the exception-handling utility, you add the file Except.cpp to your project (if you use a project-based system) or makefile (if you use MPW) and include Except.h in your source files.
If you're building your project in debug mode (the symbol
- IMPORTANT
- You must include the header Except.h in your source files before including the headers of any SOM classes (.xh files in C++). If you precompile any SOM headers, you should also precompile Except.h and put it first among the headers that you precompile.
![]()
ODDebug
is defined as1
), then Except.cpp will call functions from Crawl.cpp, and you'll need to add that source file to your project or makefile. Crawl.cpp in turn depends on ToolLibs.o (68K) or PPCToolsLib.o (PowerPC), which are part of your development system.The Exception-Handling Scheme
In the kind of exception system implemented by the exception-handling utility, an error is signaled by being thrown, or made known to the system, by using the macro callTHROW
or one of its variants. The stack then unwinds back to the point where a calling function has set up an error handler for this exception. The handler then catches, or responds to, the exception; it performs whatever recovery or cleanup is necessary. The error handler can then allow execution to continue or--more commonly--it can reraise the exception, throwing it back to the next exception handler on the stack.An exception handler in this scheme is defined as a block of code, delimited by the macro calls
TRY
,CATCH_ALL
, andENDTRY
(or their variants). Only exceptions that are thrown within the scope of theTRY
/ENDTRY
pair of a handler can be handled by that handler.The following is an example of the most basic use of this kind of exception system. It shows the exception handling involved with the fail-safe allocation of a pair of handles:
ODHandle MyNewHandle(ODSize size) { OSErr err; ODHandle h = NewTempHandle(size,&err); THROW_IF_ERROR(err); return h; }This function (MyNewHandle
) throws an exception if theNewTempHandle
function returns a nonzero error.MyNewHandle
does not itself catch any exceptions. The next function (TwoHandles
) usesMyNewHandle
to allocate the pair of handles:
void TwoHandles( ) { Handle h1, h2; h1 = MyNewHandle(10000); TRY{ h2 = MyNewHandle(10000); }CATCH_ALL{ ODDisposeHandle(h1); RERAISE; }ENDTRY ... }InTwoHandles
, if the first call toMyNewHandle
fails, it throws an error. SinceTwoHandles
has not set up an exception handler around that call, the exception is thrown out ofTwoHandles
and into its caller, and so on up the stack until an exception handler is found. None of the code shown here handles that exception.If the first call succeeds, however, execution passes to the second call to
MyNewHandle
, which is inside an exception handler. If this call fails and throws an exception, the exception is caught by the exception handler, and the block afterCATCH_ALL
executes. This block cleans up by disposing of the first allocated handle (h1
), preventing a memory leak. Thus, either both handles are allocated or neither is.After
h1
is disposed of, the exception handler callsRERAISE
, which re-throws the same exception up the stack until the next enclosing exception handler is found. (If the handler hadn't calledRERAISE
, execution would have fallen out of the exception handler to the statement followingENDTRY
.)If the second call to
MyNewHandle
succeeds, execution falls out of the entire exception handler, skipping theCATCH_ALL
block entirely (since there was no exception) and ending up at the statement immediately followingENDTRY
.Throwing Exceptions
In the OpenDoc exception-handling utility, you throw an exception by calling theTHROW
macro or one of its variants. This causes execution to jump immediately to the closest exception handler below it on the stack. These are the variants ofTHROW
:THROW
THROW
throws an exception with the error number that is supplied to it. The error can be a standard OpenDoc error or a platform-specific error. The error code must be a nonzero value; it doesn't make sense to throw an exception whose value iskODNoError
.THROW_IF_NULL
THROW_IF_NULL
throws the exceptionkODErrOutOfMemory
if a null pointer is supplied to it. Call this macro after you call a memory-allocation function (such asSOMNew
orMMNewPtr
) that returns null when there is insufficient memory.Do not use
THROW_IF_NULL
with functions that can return null for other reasons. For example, the Mac OS Resource Manager routineGetResource
returns null if the resource cannot be found; in that case, you should first callResError
to find the actual error code, and then callTHROW
.THROW_IF_ERROR
THROW_IF_ERROR
throws an exception if the error supplied to it is nonzero. If the error value iskODNoError
, nothing happens. This is a useful call to use following a function call whose return value is an error code.For example, the Mac OS File Manager function
FSpOpenDF
returns zero if it succeeds, and otherwise a nonzeroOSErr
code. Passing the result toTHROW_IF_ERROR
ensures that the right exception is thrown if the call toFSpOpenDF
fails.Exception Handlers
An exception handler consists of aTRY
block, zero or moreCATCH_ALL
blocks, and anENDTRY
:
TRY{ // statements }CATCH_ALL{ // statements }ENDTRYIt's perfectly legal lexically (and not uncommon) to nest exception handlers in a single function. Any error caught and reraised by the inner handler will be caught by the outer one.The rest of this section describes what actions each of the macro statements and its associated code block perform.
TRY
Following aTRY
macro, the immediately subsequent statements are executed. If one of the statements, or any function one of the statements calls, throws an exception that reaches this exception handler, then one of the followingCATCH_ALL
blocks may be executed. Otherwise, after the last statement in theTRY
block finishes, control passes to the statement following theENDTRY
.CATCH_ALL
If an exception is thrown to this handler, the statements following the
CATCH_ALL
macro are executed. To tell what error code was thrown, use theErrorCode
function.The flow of control for the
CATCH_ALL
is the same as forCATCH
. If no exception is raised or re-raised, control passes from the last statement in thisCATCH_ALL
block to the statement following theENDTRY
.ENDTRY
TheENDTRY
macro statement indicates the end of the exception handler. After aTRY
orCATCH
block finishes without throwing or re-raising an exception, the exception handler removes itself from the stack and control passes to the statement followingENDTRY
.RERAISE
TheRERAISE
macro statement is called within aCATCH_ALL
block. It causes the exception to be thrown again, to the next active exception handler on the stack. This is the normal behavior for an exception handler--most of the time you don't want to hide the error; you want to propagate it so a higher level handler can deal with it.The SOM Environment Parameter
OpenDoc objects are SOM objects, which means that they follow the CORBA rules for handling exceptions. Every method call made to an OpenDoc object (including your part, as a subclass ofODPart
) must therefore include an environment parameter (ev
), a pointer to a value that can describe an error. For example, theCreateLinkSource
method ofODDraft
has the following prototype (in IDL):
ODLinkSource CreateLinkSource(in ODPart part);The method takes a single parameter, of typeODPart
. To use this method, however, a caller in C++ must supply two parameters:
MyLinkSource = MyDraft->CreateLinkSource(ev, somSelf);If execution of the method results in an error condition, the receiver of the call (the draft object in this case) must place an exception code in the value pointed to byev
and return. The caller must therefore examine theev
parameter after every call to a SOM object, to see if an exception has been raised.All OpenDoc methods that you call, as well as all public methods of your part editor that you write, must return errors this way. What this means for your exception handling is that
The environment variable is passed along through a sequence of calls and can be used in calls to both SOM and C++ objects. For example, the environment variable is passed in these situations:
- you must supply an environment variable with all method calls to OpenDoc objects
- you must check the environment variable after the call returns
For more information on the environment parameter and exceptions, see SOMobjects Developer Toolkit Users Guide and SOMobjects Developer Toolkit Programmers Reference Manual from IBM.
- If your C++ method (that does not itself receive an environment parameter) calls a SOM method, it must use a SOM utility method to retrieve the environment variable.
- If your SOM method calls another SOM method, it can simply pass on the environment parameter it receives.
- If your SOM method calls a C++ method that may in turn call a SOM method, your SOM method can pass the environment parameter on to the C++ method (if the C++ method was designed to accept it; see next bullet).
- If your C++ method is called by a SOM method and in turn makes calls to SOM methods, it is best to design it to accept an environment parameter that it can then pass on.
Any exception-handling scheme that you use must support this method of passing exceptions. The OpenDoc utility described in this section helps you check the environment variable after each method call.
Handling SOM Exceptions
The exception-handling utility has some special features that simplify working with SOM. There are two reasons why these features are necessary:
This implies that the
- SOM has its own way of returning error codes, based on an environment variable, a pointer to which is passed into every method of a SOM object.
- You cannot throw an exception, or allow one to be thrown, out of a SOM method. SOM requires that a method return normally, and throwing an exception that is caught by a handler in some other function farther up the stack would violate this.
ev
parameter must be checked for an error value after every call to a SOM method and that an exception raised in a SOM method or any function it calls must be caught and its error code stored in theev
parameter. The exception-handling utility includes functions to simplify these tasks, which are variants of the exception handling macros previously introduced:
SOM_TRY SOM_CATCH_ALL SOM_ENDTRYThese macros are identical toTRY
,CATCH_ALL
, andENDTRY
, except that when they catch an exception, they store the exception value in the method'sev
parameter where the caller can see it.Because you cannot throw an exception out of a SOM method, it is illegal to
RERAISE
in theSOM_CATCH_ALL
block. You should exit the function normally by falling off the end or callingreturn
(in C++ the former generates slightly better code).
- IMPORTANT
SOM_ENDTRY
works differently thanENDTRY
in that its default behavior is to re-raise the exception by storing the error information in theEnvironment
variable so it is propagated to the caller. (WithENDTRY
you must explicitly reraise in yourCATCH_ALL
block or the exception will disappear.) If you don't want to return the exception to the caller, you must callSetErrorCode(kODNoError)
in theSOM_CATCH
block.![]()
Automatic Environment Checking
If you include the header Except.h in your source files, it defines a special preprocessor symbol that modifies the way SOM messages are sent. Any SOM headers (.xh files for development in C++) included after Except.h has been included are modified so that, after the message is sent and control returns to the caller, the environment variable (ev
) is checked and an exception raised if the variable contains an error.For example, the following code fragment does not use automatic environment checking:
#include <ODWingDing.xh>... long AFunction (Environment *ev, ODWingDing *wingDing) { long result = wingDing->Spin(ev); if(ev->_major) { // ODWingDing::Spin returns error result = 0; goto handle_error; } ... handle_error: return result; }In this example, Except.h is not included, so environment checking is not automatic, and the caller (AFunction
) can and must check the environment variable after every SOM method call.Here is the same example with automatic environment checking:
#include <Except.h> // Enables automatic ev checking #include <ODWingDing.xh>... long AFunction (Environment *ev, ODWingDing *wingDing) { long result; SOM_TRY long result = wingDing->Spin(ev); ... SOM_CATCH_ALL result = kODNULL; SOM_ENDTRY return result; }Since Except.h is included before ODWingDing.h, environment-checking code is added to the call to theSpin
method ofODWingDing
. IfSpin
encounters an error and returns error status inev
, an exception with that same error code is thrown, which will be caught by theSOM_CATCH_ALL
exception handler and in its turn returned in theev
parameter ofAFunction
.There are two important precautions to keep in mind:
- You must include Except.h before including any headers that declare SOM classes if you want to use automatic environment checking for them.
- When using automatic environment checking, any SOM method call may throw an exception, so any SOM method that calls other SOM methods must be prepared to handle exceptions.
Coding Precautions
To achieve its results as a C++ library, the OpenDoc exception-handling utility relies on complex macros and sophisticated library functions. To some extent, it "fools" the compiler. Because of this, there are some precautions you have to take to avoid causing the compiler to generate incorrect code.Very few C++ compilers have intrinsic support for exceptions, so the OpenDoc exception-handling utility is based on the ANSI
setjmp
andlongjmp
calls. Because of this basis, the compiler cannot always track the possible flow of control when exceptions are thrown and caught. The compiler can generate code that improperly fails to pop an exception handler off the stack or that makes incorrect assumptions about flow of execution.This section discusses the precautions you must follow to make sure that the complier makes no mistakes, even when you have nested exception handlers.
Make Variables That You Modify Volatile
The compiler's register allocator and optimizer can make incorrect assumptions and generate bad code unless you take this precaution: always declare as volatile any variable or parameter that you modify in aTRY
block and then use in aCATCH
orCATCH_ALL
block.The reason for this is that the compiler doesn't understand that the
TRY
block can be executed on the way to aCATCH
block, and that therefore the variable may be modified before theCATCH
block is reached. It may therefore end up using an obsolete value for the variable while in theCATCH
block. To work around this, you have to tell the compiler not to store the variable's value in a register (because it may be out of date) but always to look it up from the stack frame.The C++
volatile
keyword in the variable declaration does this (tells the compiler not to store the variable's value in a register). Unfortunately, some compilers don't implement it properly, and it can be confusing to use properly with pointer variables. For this reason the exception system defines a macro,ODVolatile,
that declares a variable to be volatile. All you have to do is put this after the variable declaration. Here's an example:
void *p = kODNULL; ODVolatile(p); TRY{ Zog1(); p = ODNewPtr(10000); Zog2(); }CATCH{ ODDisposePtr(p); RERAISE; }ENDTRYThe purpose of the exception handler is to make sure thatp
is disposed of on the way out in case it was allocated byODNewPtr
. Becausep
is modified inside theTRY
block, it has to be marked as volatile. (Note that when theCATCH
block is called,p
might still beNULL
--ifZog1
orODNewPtr
threw the exception--or it might be a valid pointer, if it wasZog2
that threw the exception. Fortunately, we pre-initializedp
tokODNULL
, andODDisposePtr
can safely be passed a null pointer. If we hadn't initializedp
, this code might crash.)
Main | Page One | What's New | Apple Computer, Inc. | Find It | Contact Us | Help